// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/devtools/protocol/security_handler.h"

#include <string>

#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/security_style_explanations.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"

namespace content {
namespace protocol {

    using Explanations = protocol::Array<Security::SecurityStateExplanation>;

    namespace {

        std::string SecurityStyleToProtocolSecurityState(
            blink::WebSecurityStyle security_style)
        {
            switch (security_style) {
            case blink::WebSecurityStyleUnknown:
                return Security::SecurityStateEnum::Unknown;
            case blink::WebSecurityStyleUnauthenticated:
                return Security::SecurityStateEnum::Neutral;
            case blink::WebSecurityStyleAuthenticationBroken:
                return Security::SecurityStateEnum::Insecure;
            case blink::WebSecurityStyleWarning:
                return Security::SecurityStateEnum::Warning;
            case blink::WebSecurityStyleAuthenticated:
                return Security::SecurityStateEnum::Secure;
            default:
                NOTREACHED();
                return Security::SecurityStateEnum::Unknown;
            }
        }

        void AddExplanations(
            const std::string& security_style,
            const std::vector<SecurityStyleExplanation>& explanations_to_add,
            Explanations* explanations)
        {
            for (const auto& it : explanations_to_add) {
                explanations->addItem(Security::SecurityStateExplanation::Create()
                                          .SetSecurityState(security_style)
                                          .SetSummary(it.summary)
                                          .SetDescription(it.description)
                                          .SetHasCertificate(it.has_certificate)
                                          .Build());
            }
        }

    } // namespace

    SecurityHandler::SecurityHandler()
        : DevToolsDomainHandler(Security::Metainfo::domainName)
        , enabled_(false)
        , host_(nullptr)
    {
    }

    SecurityHandler::~SecurityHandler()
    {
    }

    void SecurityHandler::Wire(UberDispatcher* dispatcher)
    {
        frontend_.reset(new Security::Frontend(dispatcher->channel()));
        Security::Dispatcher::wire(dispatcher, this);
    }

    void SecurityHandler::AttachToRenderFrameHost()
    {
        DCHECK(host_);
        WebContents* web_contents = WebContents::FromRenderFrameHost(host_);
        WebContentsObserver::Observe(web_contents);

        // Send an initial DidChangeVisibleSecurityState event.
        DCHECK(enabled_);
        DidChangeVisibleSecurityState();
    }

    void SecurityHandler::SetRenderFrameHost(RenderFrameHostImpl* host)
    {
        host_ = host;
        if (enabled_ && host_)
            AttachToRenderFrameHost();
    }

    void SecurityHandler::DidChangeVisibleSecurityState()
    {
        DCHECK(enabled_);

        SecurityStyleExplanations security_style_explanations;
        blink::WebSecurityStyle security_style = web_contents()->GetDelegate()->GetSecurityStyle(
            web_contents(), &security_style_explanations);

        const std::string security_state = SecurityStyleToProtocolSecurityState(security_style);

        std::unique_ptr<Explanations> explanations = Explanations::create();
        AddExplanations(Security::SecurityStateEnum::Insecure,
            security_style_explanations.broken_explanations,
            explanations.get());
        AddExplanations(Security::SecurityStateEnum::Neutral,
            security_style_explanations.unauthenticated_explanations,
            explanations.get());
        AddExplanations(Security::SecurityStateEnum::Secure,
            security_style_explanations.secure_explanations,
            explanations.get());
        AddExplanations(Security::SecurityStateEnum::Info,
            security_style_explanations.info_explanations,
            explanations.get());

        std::unique_ptr<Security::InsecureContentStatus> insecure_status = Security::InsecureContentStatus::Create()
                                                                               .SetRanMixedContent(security_style_explanations.ran_mixed_content)
                                                                               .SetDisplayedMixedContent(
                                                                                   security_style_explanations.displayed_mixed_content)
                                                                               .SetRanContentWithCertErrors(
                                                                                   security_style_explanations.ran_content_with_cert_errors)
                                                                               .SetDisplayedContentWithCertErrors(
                                                                                   security_style_explanations.displayed_content_with_cert_errors)
                                                                               .SetRanInsecureContentStyle(SecurityStyleToProtocolSecurityState(
                                                                                   security_style_explanations.ran_insecure_content_style))
                                                                               .SetDisplayedInsecureContentStyle(
                                                                                   SecurityStyleToProtocolSecurityState(
                                                                                       security_style_explanations
                                                                                           .displayed_insecure_content_style))
                                                                               .Build();

        frontend_->SecurityStateChanged(
            security_state,
            security_style_explanations.scheme_is_cryptographic,
            std::move(explanations),
            std::move(insecure_status),
            Maybe<std::string>(security_style_explanations.summary));
    }

    Response SecurityHandler::Enable()
    {
        enabled_ = true;
        if (host_)
            AttachToRenderFrameHost();

        return Response::OK();
    }

    Response SecurityHandler::Disable()
    {
        enabled_ = false;
        WebContentsObserver::Observe(nullptr);
        return Response::OK();
    }

    Response SecurityHandler::ShowCertificateViewer()
    {
        if (!host_)
            return Response::InternalError();
        WebContents* web_contents = WebContents::FromRenderFrameHost(host_);
        scoped_refptr<net::X509Certificate> certificate = web_contents->GetController().GetVisibleEntry()->GetSSL().certificate;
        if (!certificate)
            return Response::Error("Could not find certificate");
        web_contents->GetDelegate()->ShowCertificateViewerInDevTools(
            web_contents, certificate);
        return Response::OK();
    }

} // namespace protocol
} // namespace content
